﻿using System;
using System.Collections.Generic;
using System.Linq;
using System.Text;
using MSpecLauncher.Model;
using System.Xml.Linq;
using System.IO;
using System.Xml;

namespace MSpecLauncher.Services
{
    /// <summary>
    /// Creates model classes from a given text
    /// </summary>
    public class MSpecModelBuilder : IModelBuilder
    {
        /// <summary>
        /// Given a XML test result in string format, it builds a model class with the data
        /// </summary>
        public SpecRun Build(string xmlText)
        {
            XmlDocument doc = new XmlDocument();
            doc.LoadXml(xmlText);

            // Nos quedamos con la parte del documento XML referente a MSpec
            XmlNode mspec = doc.SelectNodes("MSpec")[0];

            string date = "";
            int runtime = -1;

            // Get date and duration if present
            int nodeIndex = 0;
            if (mspec.ChildNodes[nodeIndex].Name == "generated")
            {
                date = mspec.ChildNodes[0].InnerText;
                nodeIndex++;
            }

            if (mspec.ChildNodes[nodeIndex].Name == "run")
            {
                runtime = int.Parse(mspec.ChildNodes[nodeIndex].Attributes["time"].InnerText);
            }

            // Create model for every Assembly
            IEnumerable<AssemblySpecs> assemblySpecs = this.BuildAssemblySpecs(mspec.SelectNodes("assembly"));

            // Create model for main object
            SpecRun specRun = new SpecRun(date, runtime, assemblySpecs);

            return specRun;
        }

        private IEnumerable<AssemblySpecs> BuildAssemblySpecs(XmlNodeList nodes)
        {
            List<AssemblySpecs> assemblies = new List<AssemblySpecs>();

            foreach (XmlNode node in nodes)
            {
                string name = node.Attributes["name"].InnerText;
                string location = node.Attributes["location"].InnerText;
                IEnumerable<SpecSubject> subjects = this.BuildSpecSubjects(node.SelectNodes("concern"));

                assemblies.Add(new AssemblySpecs(name, location, subjects));
            }

            return assemblies;
        }

        private IEnumerable<SpecSubject> BuildSpecSubjects(XmlNodeList nodes)
        {
            List<SpecSubject> subjects = new List<SpecSubject>();

            foreach (XmlNode node in nodes)
            {
                string name = node.Attributes["name"].InnerText;
                IEnumerable<SpecContext> contexts = this.BuildSpecContexts(node.SelectNodes("context"));

                subjects.Add(new SpecSubject(name, contexts));
            }

            return subjects;
        }

        private IEnumerable<SpecContext> BuildSpecContexts(XmlNodeList nodes)
        {
            List<SpecContext> contexts = new List<SpecContext>();

            foreach (XmlNode node in nodes)
            {
                string name = node.Attributes["name"].InnerText;
                int time = int.Parse(node.Attributes["time"].InnerText);
                IEnumerable<Spec> specs = this.BuildSpecs(node.SelectNodes("specification"));

                contexts.Add(new SpecContext(name, time, specs));
            }

            return contexts;
        }

        private IEnumerable<Spec> BuildSpecs(XmlNodeList nodes)
        {
            List<Spec> specs = new List<Spec>();

            foreach (XmlNode node in nodes)
            {
                string name = node.Attributes["name"].InnerText;
                int time = int.Parse(node.Attributes["time"].InnerText);
                string statusStr = node.Attributes["status"].InnerText;
                ESpecStatus status =
                    statusStr == "passed" ? ESpecStatus.Passed :
                    statusStr == "ignored" ? ESpecStatus.Ignored :
                    statusStr == "failed" ? ESpecStatus.Failed :
                    statusStr == "not-implemented" ? ESpecStatus.NotImplemented : ESpecStatus.Unknown;
                string output = node.HasChildNodes ? node.ChildNodes[0].InnerText : "";

                IEnumerable<SpecError> errors = this.BuildSpecErrors(node.SelectNodes("error"));

                specs.Add(new Spec(name, time, status, output, errors));
            }

            return specs;
        }

        private IEnumerable<SpecError> BuildSpecErrors(XmlNodeList nodes)
        {
            List<SpecError> errors = new List<SpecError>();

            foreach (XmlNode node in nodes)
            {
                string type = "";
                string message = "";
                string stackTrace = "";

                XmlNodeList childs = node.ChildNodes;
                foreach (XmlNode child in childs)
                {
                    if (child.Name == "type") type = child.InnerText;
                    if (child.Name == "message") message = child.InnerText;
                    if (child.Name == "stack-trace") stackTrace = child.InnerText;
                }                

                errors.Add(new SpecError(type, message, stackTrace));
            }

            return errors;
        }

    }
}
